/* vim:ft=objc:
 * $Id: PreferencesController.m 123 2005-05-17 05:59:18Z bmoore $
 *
 * Copyright (c) 2004, 2005  Branden J. Moore.
 *
 * This file is part of MacBiff, and is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * MacBiff is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MacBiff; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 *
 */

#import "PreferencesController.h"

#if 0
#  define EBUG 1
#endif
#include "debug.h"
#include "xmalloc.h"

PreferencesController *prefs = Nil;


@interface NSObject (MutableDeepCopy)

- mutableDeepCopy;

@end

@implementation NSObject (MutableDeepCopy)

- mutableDeepCopy
{
	if([self respondsToSelector:@selector(mutableCopyWithZone:)])
		return [self mutableCopy];
	else if([self respondsToSelector:@selector(copyWithZone:)])
		return [self copy];
	else
		return [self retain];
}

@end

@implementation NSDictionary (MutableDeepCopy)

- mutableDeepCopy
{
	NSMutableDictionary *newDictionary = [[NSMutableDictionary alloc] init];
	NSEnumerator *enumerator = [self keyEnumerator];
	id key;
	while((key = [enumerator nextObject])) {
		id obj = [[self objectForKey:key] mutableDeepCopy];
		[newDictionary setObject:obj forKey:key];
		[obj release];
	}
	return newDictionary;
}

@end

@implementation NSArray (MutableDeepCopy)

- mutableDeepCopy
{
	NSMutableArray *newArray = [[NSMutableArray alloc] init];
	NSEnumerator *enumerator = [self objectEnumerator];
	id obj;
	while((obj = [enumerator nextObject])) {
		obj = [obj mutableDeepCopy];
		[newArray addObject:obj];
		[obj release];
	}
	return newArray;
}

@end

@implementation NSSet (MutableDeepCopy)

- mutableDeepCopy
{
	NSMutableSet *newSet = [[NSMutableSet alloc] init];
	NSEnumerator *enumerator = [self objectEnumerator];
	id obj;
	while((obj = [enumerator nextObject])) {
		obj = [obj mutableDeepCopy];
		[newSet addObject:obj];
		[obj release];
	}
	return newSet;
}

@end


@implementation PreferencesController


- (NSMutableDictionary*) mutableDictFromPrefs: (NSString*)prName
{
	NSMutableDictionary *dict = [[myPrefs dictionaryForKey: prName] mutableDeepCopy];
	return [dict autorelease];
}


- (void) registerDefaultPreferences
{
	NSMutableDictionary *dict = Nil;
	NSMutableDictionary *dict2 = Nil;
	NSMutableArray *arr = Nil;

	/* Alert Preferences */
	arr = [NSMutableArray arrayWithCapacity: 5];
	[myPrefs setObject: arr forKey: @"Default Pre-Check Actions"];

	arr = [NSMutableArray arrayWithCapacity: 5];
	[arr addObject: @"Sound"];
	[myPrefs setObject: arr forKey: @"Default Post-Check Actions"];

	/* PlugIn Preferences */
	dict = [NSMutableDictionary dictionaryWithCapacity: 3];

	dict2 = [NSMutableDictionary dictionaryWithCapacity: 2];
	[dict2 setObject: @"System Beep" forKey: @"Sound"];
	[dict2 setObject: [NSNumber numberWithBool: YES] forKey: @"Use System Beep"];
	[dict setObject: dict2 forKey: @"Sound"];

	dict2 = [NSMutableDictionary dictionaryWithCapacity: 1];
	[dict2 setObject: @"" forKey: @"Pre Command"];
	[dict2 setObject: @"" forKey: @"Post Command"];
	[dict2 setObject: [NSNumber numberWithBool: YES] forKey: @"Per Folder"];
	[dict setObject: dict2 forKey: @"Shell Command"];

	dict2 = [NSMutableDictionary dictionaryWithCapacity: 1];
	[dict2 setObject: @"%a" forKey: @"Title String"];
	[dict2 setObject: @"%a: %n unread message(s) in %l" forKey: @"String"];
	[dict2 setObject: [NSNumber numberWithInt: 0] forKey: @"Notify Verbosity"];
	[dict setObject: dict2 forKey: @"Growl"];

	[myPrefs setObject: dict forKey: @"Action Prefs"];


	/* Display Preferences */
	dict = [NSMutableDictionary dictionaryWithCapacity: 4];
	[dict setObject: [NSNumber numberWithBool: YES] forKey: @"Show Text"];
	[dict setObject: [NSNumber numberWithBool: YES] forKey: @"Show Total"];
	[dict setObject: [NSNumber numberWithBool: NO] forKey: @"Show Icon"];
	[dict setObject: [NSNumber numberWithBool: YES] forKey: @"Change Color"];
	[dict setObject: [NSNumber numberWithInt: NSMixedState] forKey: @"Detach State"];
	[myPrefs setObject: dict forKey: @"Display Prefs"];

	/* Misc. Preferences */
	dict = [NSMutableDictionary dictionaryWithCapacity: 2];
	[dict setObject: @"Mail.app" forKey: @"Mail App"];
	[dict setObject: [NSNumber numberWithBool: YES] forKey: @"Show Activity On Check"];
	[myPrefs setObject: dict forKey: @"Misc Prefs"];

	/* Accounts, or lack thereof */
	[myPrefs setObject: [NSDictionary dictionary] forKey: @"Accounts"];

	[myPrefs setInteger: PREFS_REVISION forKey: @"Preference Revision"];
	[myPrefs synchronize];
}


- (void) movePreference: (NSString*) fName
		 toDict: (NSMutableDictionary*) dict
	    withNewName: (NSString*) tName
{
	dprintf("--> %s\n", __func__); [prefsLock lock];
	[dict setObject: [myPrefs objectForKey: fName] forKey: tName];
	[myPrefs removeObjectForKey: fName];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (void) upgradeToRev2
{
	NSMutableDictionary *plugInsDict = Nil;
	NSMutableDictionary *plugInDict = Nil;
	NSMutableArray *arr = Nil;
	NSMutableDictionary *menuDict = Nil;
	NSMutableDictionary *miscellaneousDict = Nil;
	NSMutableDictionary *acctsDict = Nil;
	NSArray *acctNames = Nil;
	NSMutableDictionary *acctGlobalDict = Nil;
	int accounts, i;


	/* Action PlugIn Preferences */
	/* Didn't have any pre-check actions before */
	arr = [NSMutableArray arrayWithCapacity: 1];
	[myPrefs setObject: arr forKey: @"Default Pre-Check Actions"];

	/* Figure out which post-check should be used */
	arr = [NSMutableArray arrayWithCapacity: 5];
	if ( [myPrefs boolForKey: @"Alert Sound"] )
		[arr addObject: @"Sound"];
	[myPrefs removeObjectForKey: @"Alert Sound"];
	if ( [myPrefs boolForKey: @"Notify with Growl"] )
		[arr addObject: @"Growl"];
	[myPrefs removeObjectForKey: @"Notify with Growl"];
	if (! [[myPrefs stringForKey: @"New Unread Mail Command"] isEqual: @""] )
		[arr addObject: @"Shell Command"];
	[myPrefs setObject: arr forKey: @"Default Post-Check Actions"];

	/* Plug In Prefs */
	plugInsDict = [NSMutableDictionary dictionaryWithCapacity: 3];
	plugInDict = [NSMutableDictionary dictionaryWithCapacity: 3];
	[self movePreference: @"Sound Name"
		       toDict: plugInDict withNewName: @"Sound"];
	[plugInDict setObject: [NSNumber numberWithBool:
			[[plugInDict objectForKey: @"Sound"]
				isEqual: @"System Beep"]]
	      forKey: @"Use System Beep"];
	[plugInsDict setObject: plugInDict forKey: @"Sound"];

	plugInDict = [NSMutableDictionary dictionaryWithCapacity: 1];
	[plugInDict setObject: @"%a" forKey: @"Title String"];
	[plugInDict setObject: @"%a: %n new message(s) in %l" forKey: @"String"];
	[plugInDict setObject: [NSNumber numberWithInt: 0] forKey: @"Notify Verbosity"];
	[plugInsDict setObject: plugInDict forKey: @"Growl"];

	plugInDict = [NSMutableDictionary dictionaryWithCapacity: 1];
	[plugInDict setObject: @"" forKey: @"Pre Command"];
	[plugInDict setObject: [myPrefs stringForKey: @"New Unread Mail Command"]
		forKey: @"Post Command"];
	[plugInDict setObject: [NSNumber numberWithBool: YES] forKey: @"Per Folder"];
	[plugInsDict setObject: plugInDict forKey: @"Shell Command"];
	[myPrefs removeObjectForKey: @"New Unread Mail Command"];
	[myPrefs setObject: plugInsDict forKey: @"Action Prefs"];


	/* Menu Display Preferences */
	menuDict = [NSMutableDictionary dictionaryWithCapacity: 3];
	[self movePreference: @"Alert Color"
		       toDict: menuDict withNewName: @"Change Color"];
	[self movePreference: @"Hide Text"
		       toDict: menuDict withNewName: @"Show Text"];
	[menuDict setObject: [NSNumber numberWithBool:
				![[menuDict objectForKey: @"Show Text"] boolValue]]
			forKey: @"Show Text"];
	[self movePreference: @"Hide Total"
		       toDict: menuDict withNewName: @"Show Total"];
	[menuDict setObject: [NSNumber numberWithBool:
				![[menuDict objectForKey: @"Show Total"] boolValue]]
			forKey: @"Show Total"];
	[self movePreference: @"Show Icon"
		       toDict: menuDict withNewName: @"Show Icon"];
	[menuDict setObject: [NSNumber numberWithInt: NSMixedState]
			forKey: @"Detach State"];
	[myPrefs setObject: menuDict forKey: @"Display Prefs"];

	/* Miscellaneous Preferences */
	miscellaneousDict = [NSMutableDictionary dictionaryWithCapacity: 2];
	[self movePreference: @"Mail App"
		       toDict: miscellaneousDict withNewName: @"Mail App"];
	[self movePreference: @"Show Activity"
		       toDict: miscellaneousDict withNewName: @"Show Activity On Check"];
	[myPrefs setObject: miscellaneousDict forKey: @"Misc Prefs"];

	/* Account Information */
	/* First, fill in the formerly "global" preferences, we'll copy
	 * these into the individual accounts later
	 */
	acctGlobalDict = [NSMutableDictionary dictionaryWithCapacity: 4];
	[self movePreference: @"Fetch Unread Headers"
		       toDict: acctGlobalDict withNewName: @"Fetch Headers"];
	[self movePreference: @"Fetch Ignored Headers"
		       toDict: acctGlobalDict withNewName: @"Fetch Masked Headers"];
	[self movePreference: @"checkDelay"
		       toDict: acctGlobalDict withNewName: @"Check Interval"];
	/* This is No longer a preference
	 * [self movePreference: @"Ignore Ignores"
	 *		  toDict: acctGlobalDict: withNewName: @""];
	 */
	[myPrefs removeObjectForKey: @"Ignore Ignores"];
	[acctGlobalDict setObject: [NSNumber numberWithBool: YES]
			   forKey: @"Use Default Actions"];

	/* Now we do each account */
	accounts = [myPrefs integerForKey: @"Server Count"];
	[myPrefs removeObjectForKey: @"Server Count"];
	acctsDict = [NSMutableDictionary dictionaryWithCapacity: accounts];
	acctNames = [myPrefs arrayForKey: @"Server Names"];
	[myPrefs removeObjectForKey: @"Server Names"];

	for ( i = 0 ; i < accounts ; ++i ) {
		NSString *acctName = Nil;
		NSString *tempString = Nil;
		NSMutableDictionary *acctDict = Nil;

		acctName = [acctNames objectAtIndex: i];
		dprintf("Migrating Account '%s'\n", [acctName cString]);

		acctDict = [NSMutableDictionary dictionaryWithCapacity: 15];
		/* Default to IMAP, 'cause that's all we did before */
		[acctDict setObject: @"IMAP" forKey: @"Type"];

		/* Add formerly global information */
		[acctDict addEntriesFromDictionary: acctGlobalDict];

		/* Add All-Account information */
		tempString = [NSString stringWithFormat: @"disabledBoxes[%@]",
			   acctName];
		[self movePreference: tempString
				toDict: acctDict
				withNewName: @"Disabled Folders"];
		tempString = [NSString stringWithFormat: @"ignoredBoxes[%@]",
			   acctName];
		[self movePreference: tempString
				toDict: acctDict
				withNewName: @"Masked Folders"];

		/* IMAP-specific information */
		tempString = [NSString stringWithFormat: @"server[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Hostname"];
		tempString = [NSString stringWithFormat: @"username[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Username"];

		tempString = [NSString stringWithFormat: @"initServer[%@]",
			   acctName];
		[myPrefs removeObjectForKey: tempString];
		tempString = [NSString stringWithFormat: @"enabled[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Enabled"];
		tempString = [NSString stringWithFormat: @"mode[%@]",
			   acctName];
		int useSSL = [myPrefs integerForKey: tempString];
		[myPrefs removeObjectForKey: tempString];
		[acctDict setObject: [NSNumber numberWithBool: (useSSL == 4)]
			     forKey: @"Use SSL"];
		tempString = [NSString stringWithFormat: @"port[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Port"];
		tempString = [NSString stringWithFormat: @"prefix[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Path Prefix"];
		tempString = [NSString stringWithFormat: @"sep[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Folder Separator"];
		tempString = [NSString stringWithFormat: @"subscribed[%@]",
			   acctName];
		[self movePreference: tempString
			       toDict: acctDict withNewName: @"Subscribed Folders Only"];
		/* We no longer support non-stored Passwords */
		tempString = [NSString stringWithFormat: @"storedPW[%@]",
			   acctName];
		[myPrefs removeObjectForKey: tempString];

		NSArray *preActs = [NSArray array];
		NSArray *postActs = [NSArray array];
		[acctDict setObject: preActs forKey: @"Pre-Check Actions"];
		[acctDict setObject: postActs forKey: @"Post-Check Actions"];
		dprintf("Setting dictionary for '%s'\n", [acctName cString]);
		[acctsDict setObject: acctDict forKey: acctName];
	}
	[myPrefs setObject: acctsDict forKey: @"Accounts"];

	[myPrefs setInteger: 2 forKey: @"Preference Revision"];
	[myPrefs synchronize];
}


- (void) upgradePrefsFromRevision: (int) old_rev
{
	/* Check "Server Count", because we used to use it to determine if
	 * this was the first time MacBiff was run
	 */
	if ( !old_rev && ![myPrefs integerForKey: @"Server Count"] ) {
		[self registerDefaultPreferences];
	} else {
		NSAlert *alert;
		alert = [NSAlert alertWithMessageText: @"Upgrading Preferences"
					defaultButton: @"OK"
				      alternateButton: Nil
					  otherButton: Nil
			    informativeTextWithFormat:
			     	@"About to upgrade your MacBiff preferences from Revision %d to Revision %d.", old_rev, PREFS_REVISION];
		[alert runModal];

		if ( old_rev < 2 ) {
			[self upgradeToRev2];
		}
	}
}




- (id) init
{
	if ( ( self = [super init] ) != Nil ) {
		prefsLock = [[NSRecursiveLock alloc] init];
		dprintf("--> %s\n", __func__); [prefsLock lock];
		/* We want to be locked until we have gotten
		 * the preferences into a stable state.
		 */
		myPrefs = [NSUserDefaults standardUserDefaults];

		int prefs_rev = 0;

		prefs_rev = [myPrefs integerForKey: @"Preference Revision"];
		dprintf("Found Preferences Revision %d\n", prefs_rev);

		if ( prefs_rev < PREFS_REVISION ) {
			[self upgradePrefsFromRevision: prefs_rev];
		}

		prefs = self;

		/* Open Up the preferences */
		displayDict = [[self mutableDictFromPrefs: @"Display Prefs"] retain];
		miscDict = [[self mutableDictFromPrefs: @"Misc Prefs"] retain];
		accountsDict = [[self mutableDictFromPrefs: @"Accounts"] retain];
		actionsDict = [[self mutableDictFromPrefs: @"Action Prefs"] retain];

		dprintf("%d accounts, %d actions\n", [accountsDict count], [actionsDict count]);

		/* Unlock when we are done */
		dprintf("<-- %s\n", __func__); [prefsLock unlock];
	}
	return self;
}


- (void) dealloc
{
	[self commit];
	[myPrefs synchronize];

	dprintf("<-- %s\n", __func__); [prefsLock unlock];
	[prefsLock release];
	prefsLock = Nil;

	[displayDict release];
	[miscDict release];
	[accountsDict release];
	[actionsDict release];

	[super dealloc];
}


- (void) commit
{
	[self commitDisplay];
	[self commitMisc];
	[self commitAccounts];
}


- (NSMutableDictionary*) display
{
	return displayDict;
}


- (void) commitDisplay
{
	dprintf("--> %s\n", __func__); [prefsLock lock];
	[myPrefs setObject: displayDict forKey: @"Display Prefs"];
	[myPrefs synchronize];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (NSMutableDictionary*) misc
{
	return miscDict;
}


- (void) commitMisc
{
	dprintf("--> %s\n", __func__); [prefsLock lock];
	[myPrefs setObject: miscDict forKey: @"Misc Prefs"];
	[myPrefs synchronize];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (NSArray*) accountList
{
	return [accountsDict allKeys];
}


- (NSMutableDictionary*) account: (NSString*) account;
{
	return [accountsDict objectForKey: account];
}


- (void) commitAccounts
{
	dprintf("--> %s\n", __func__); [prefsLock lock];
	[myPrefs setObject: accountsDict forKey: @"Accounts"];
	[myPrefs synchronize];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (void) removeAccount: (NSString*) account
{
	[accountsDict removeObjectForKey: account];
}


- (void) renameAccount: (NSString*) a1 to: (NSString*) a2
{
	dprintf("--> %s\n", __func__); [prefsLock lock];
	NSDictionary *acct = [accountsDict objectForKey: a1];
	[accountsDict setObject: acct forKey: a2];
	[accountsDict removeObjectForKey: a1];
	[self commitAccounts];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (void) addAccount: (NSString*) account withType: (NSString*) type;
{
	NSMutableDictionary *acct = [[NSMutableDictionary alloc] initWithCapacity: 3];
	NSMutableDictionary *mask = [[NSMutableDictionary alloc] initWithCapacity: 1];
	NSMutableDictionary *disable = [[NSMutableDictionary alloc] initWithCapacity: 1];
	NSMutableArray *preActs = [[NSMutableArray alloc] initWithCapacity: 1];
	NSMutableArray *postActs = [[NSMutableArray alloc] initWithCapacity: 1];

	dprintf("--> %s\n", __func__); [prefsLock lock];
	[acct setObject: type forKey: @"Type"];
	[acct setObject: mask forKey: @"Masked Folders"];
	[acct setObject: disable forKey: @"Disabled Folders"];
	[acct setObject: preActs forKey: @"Pre-Check Actions"];
	[acct setObject: postActs forKey: @"Post-Check Actions"];
	[acct setObject: [NSNumber numberWithBool: YES] forKey: @"Use Default Actions"];
	[acct setObject: [NSNumber numberWithBool: NO] forKey: @"Enabled"];
	[acct setObject: [NSNumber numberWithInt: 5] forKey: @"Check Interval"];
	[accountsDict setObject: acct forKey: account];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (NSMutableDictionary*) action: (NSString*) action;
{
	return [actionsDict objectForKey: action];
}


- (void) commitActions
{
	dprintf("--> %s\n", __func__); [prefsLock lock];
	[myPrefs setObject: actionsDict forKey: @"Action Prefs"];
	[myPrefs synchronize];
	dprintf("<-- %s\n", __func__); [prefsLock unlock];
}


- (NSArray*) defaultPreCheckActions
{
	return [myPrefs objectForKey: @"Default Pre-Check Actions"];
}


- (void) setDefaultPreCheckActions: (NSArray*) arr
{
	[myPrefs setObject: arr forKey: @"Default Pre-Check Actions"];
}


- (NSArray*) defaultPostCheckActions
{
	return [myPrefs objectForKey: @"Default Post-Check Actions"];
}


- (void) setDefaultPostCheckActions: (NSArray*) arr
{
	[myPrefs setObject: arr forKey: @"Default Post-Check Actions"];
}


@end
